#include "Context.hpp"

namespace vlk
{
    std::unique_ptr<Context> Context::instance_ = nullptr;

    Context &Context::GetInstance()
    {
        return *instance_;
    }

    void Context::Init(const char *const *extensions, CreateSurfaceFunction callBack)
    {
        if (!instance_)
        {
            instance_.reset(new Context(extensions, callBack));
        }
    }
    void Context::Quit()
    {
        if (instance_)
        {
            instance_.reset();
        }
    }
    Context::Context(const char *const *extensions, CreateSurfaceFunction callBack)
    {
        // 1. Create vulkan instance
        createVkInstance(extensions);
        // 2. Pick a physical device
        pickPhysicalDevice();
        // 3. Create surface
        createSurface(callBack);
        // 4. Query queue family indicates
        queryVulkanQueueFamilyIndicates();
        // 5. Create logical device
        createLogicalDevice();
        // 6. Get queue
        getQueue();
    }
    Context::~Context()
    {
        /* Destroy swap chain*/
        swapChain_.reset();
        /* Destroy surface*/
        vkInstance.destroySurfaceKHR(surface);
        /* Destroy logical device and queue*/
        logicDevice.destroy();
        /* Destroy vulkan instance */
        vkInstance.destroy();
    }

    void Context::createVkInstance(const char *const *extensions)
    {
        vk::ApplicationInfo appInfo;
        appInfo.setApiVersion(VK_API_VERSION_1_3);

        /* Layer */
        // std::vector<const char *> layers = {"VK_LAYER_KHRONOS_valication"};

        vk::InstanceCreateInfo createInfo;
        createInfo.setPApplicationInfo(&appInfo)
            .setPpEnabledExtensionNames(extensions);
        // .setPEnabledLayerNames(layers);
        /* Create vulkan instance */
        vkInstance = vk::createInstance(createInfo);
        if (!vkInstance)
        {
            throw std::runtime_error("failed to create vulkan instance!");
        }
    }

    void Context::pickPhysicalDevice()
    {
        if (vkInstance)
        {
            auto physicalDevices = vkInstance.enumeratePhysicalDevices();
            physicalDevie = physicalDevices[0];
            std::cout << "Physical device is " << physicalDevie.getProperties().deviceName << std::endl;
        }
    }

    void Context::createLogicalDevice()
    {
        std::array extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
        vk::DeviceCreateInfo logicalDeviceCreateInfo;
        std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
        float priority = 1.0;

        vk::DeviceQueueCreateInfo queueCreateInfo;
        queueCreateInfo.setQueuePriorities(priority)
            .setQueueCount(1)
            .setQueueFamilyIndex(queueFamilyIndicates.graphicQueue.value());
        queueCreateInfos.push_back(std::move(queueCreateInfo));
        if (queueFamilyIndicates.graphicQueue.value() == queueFamilyIndicates.presentQueue.value())
        {
            vk::DeviceQueueCreateInfo queueCreateInfo2;
            queueCreateInfo2.setQueuePriorities(priority)
                .setQueueCount(1)
                .setQueueFamilyIndex(queueFamilyIndicates.presentQueue.value());
            queueCreateInfos.push_back(queueCreateInfo2);
        }

        logicalDeviceCreateInfo.setQueueCreateInfos(queueCreateInfos)
                               .setPEnabledExtensionNames(extensions);

        logicDevice = physicalDevie.createDevice(logicalDeviceCreateInfo);
    }

    void Context::queryVulkanQueueFamilyIndicates()
    {
        auto properties = physicalDevie.getQueueFamilyProperties();
        for (int i = 0; i < properties.size(); i++)
        {
            const auto &property = properties[i];
            if (property.queueFlags & vk::QueueFlagBits::eGraphics)
            {
                queueFamilyIndicates.graphicQueue = i;
            }
            if (physicalDevie.getSurfaceSupportKHR(i, surface))
            {
                queueFamilyIndicates.presentQueue = i;
            }
            if (queueFamilyIndicates.graphicQueue.has_value() && queueFamilyIndicates.presentQueue.has_value())
            {
                break;
            }
        }
    }

    void Context::getQueue()
    {
        graphicQueue = logicDevice.getQueue(queueFamilyIndicates.graphicQueue.value(), 0);
        presentQueue = logicDevice.getQueue(queueFamilyIndicates.presentQueue.value(), 0);
    }

    void Context::createSurface(CreateSurfaceFunction callBack)
    {
        // ----- Create the window surface -----
        surface = callBack(vkInstance);
    }

    void Context::initSwapChain(int w, int h) {
        swapChain_.reset(new SwapChain(w, h));
    }
}